package it.softphone.cmc.server.service.impl;

import it.softphone.cmc.server.entity.ScriptletAssetEntity;
import it.softphone.cmc.server.service.BundleService;
import it.softphone.cmc.server.service.ScriptletManagerService;
import it.softphone.cmc.shared.entity.Asset;
import it.softphone.cmc.shared.entity.AssetImpl;
import it.softphone.cmc.shared.entity.ScriptletAsset;
import it.softphone.commons.exception.NullParameterException;
import it.softphone.commons.json.server.JSONException;
import it.softphone.commons.json.server.JSONObject;
import it.softphone.commons.json.server.JSONUtils;
import it.softphone.commons.service.LogAware;
import it.softphone.commons.service.scripting.ScriptingService;

import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.appengine.api.datastore.QueryResultIterable;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;
import com.googlecode.objectify.helper.DAOBase;

/**
 * 
 * @author softphone
 *
 */
@Service
public class GAEScriptletManagerServiceImpl extends DAOBase implements ScriptletManagerService, LogAware {

	//@Autowired private EntityFactory _ef;

	@Autowired ScriptingService _sscript;
	@Autowired BundleService _sbundle;

	//private static Pattern p = Pattern.compile("(?:\\s*)//@include(?:\\s+)(\\w+)");
	static final String ws = "(?:\\s*)";
	
	static final Pattern p = Pattern.compile( new StringBuilder()
			.append(ws)
			.append("includeScriptlet\\(")
			.append(ws)
			.append("['\"]([\\w:\\d]+)['\"]")
			.append(ws)
			.append("\\)")
			.append(ws)
			.append(';')
			.append(ws)
			.toString()		
			);
	
	
	@PostConstruct
	public void init() {
	
		ObjectifyService.register(ScriptletAssetEntity.class);
		
	}

	/**
	 * 
	 */
	@Override
	public ScriptletAsset makeScriptlet() {
		return new ScriptletAssetEntity();
	}

	/**
	 * 
	 */
	@Override
	public ScriptletAsset makeScriptletFromJson(JSONObject object)	throws JSONException {

		return JSONUtils.copyTo(new ScriptletAssetEntity(), object);
	
	}

	/**
	 * 
	 */
	@Override
	public void saveScriptlet(ScriptletAsset s) {
		if( null == s ) throw new NullParameterException("s");
		
		if( s.getId() == null ) {
			s.setId( AssetImpl.toKey(s));
		}
		
		super.ofy().put( s );
		
	}

	/**
	 * 
	 */
	@Override
	public ScriptletAsset findScriptletByKey(String key) {
		if( null==key ) throw new NullParameterException("key");
		
		try {
			
			ScriptletAssetEntity result =  super.ofy().get( ScriptletAssetEntity.class, key );

			return result;
			
		}
		catch( com.googlecode.objectify.NotFoundException e ) {
			//TOD LOG
			logger.debug( String.format("Scriptlet not found [%s]", key) );
		}

		return null;
		
		
	}
	
	/**
	 * 
	 */
	@Override
	public List<String> findScriptletKeys(Asset filter) {
		if( null==filter ) throw new NullParameterException("filter");
		
		java.util.List<String> result = new java.util.ArrayList<String>();
		
		Query<ScriptletAssetEntity> query =  super.ofy().query(ScriptletAssetEntity.class);
		
		if( filter.getGroupId() !=null &&  !filter.getGroupId().isEmpty()) {
			query.filter("groupId = ", filter.getGroupId() );
		}
		if( filter.getArtifactId() !=null && !filter.getArtifactId().isEmpty()) {
			query.filter("artifactId = ", filter.getArtifactId() );
		}
		if( filter.getVersion() !=null && !filter.getVersion().isEmpty() ) {
			query.filter("version = ", filter.getVersion() );
		}
		
		QueryResultIterable<Key<ScriptletAssetEntity>> i = query.fetchKeys();
		
		for( Key<ScriptletAssetEntity> k : i ) {
			
			final String name = k.getName();
			
			result.add( name );

		}
		
		Collections.sort(result);
		
		return result;
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
    protected String processIncludes( ScriptletAsset s ) {
        
    	java.util.Set<String> includeSet = new java.util.HashSet<String>(10);

    	StringBuilder sb = new StringBuilder();
    	
    	processIncludes( s, sb, includeSet );
    	
    	if( logger.isDebugEnabled() ) {
	    	for( String include : includeSet  ) {
	    		logger.debug( String.format("include script [%s]!", include ));
	    	}
    	}

		return sb.toString();
    }
    
    /**
     * 
     * @param service
     * @param s
     * @param sb
     * @param includeSet
     * @return
     */
    protected StringBuilder processIncludes( ScriptletAsset s, StringBuilder sb, java.util.Set<String> includeSet ) {
           	
    	includeSet.add( s.getId() );
    	
		java.util.Set<String> dependencies = s.getDependencies();
		
		for( String id : dependencies ) {

			if( includeSet.contains(id) ) {
				logger.warn( String.format("include script [%s] has been already included!", id ));
				continue;
			}
			
			ScriptletAsset is = findScriptletByKey( id );
			
			if( is == null ) {
				logger.warn( String.format("include script [%s] not found!", id ));
				continue;						
			}
			processIncludes( is, sb, includeSet );

		}
			    	
		return sb.append('\n').append(s.getContent());
		
    }
	
    /**
     * 
     */
	@Override
	public Object execScriptlet(ScriptletAsset s, Object... args) throws ScriptException, NoSuchMethodException {
		logger.debug( String.format("exec scriptlet [%s]",s.getId() ));

		final String contents = processIncludes(s);

		final ClassLoader cl = Thread.currentThread().getContextClassLoader();
		Thread.currentThread().setContextClassLoader( getCL(s) );
		
		try {
			_sscript.put(ScriptEngine.FILENAME, s.getId());
			_sscript.eval(contents);
	
			logger.trace( String.format(">>\n%s\n<<end scriptlet [%s]", contents,  s.getId()));
	
			Object sresult = _sscript.invokeFunction("main", (Object[])args);
			
			return sresult;
		
		}finally {
			Thread.currentThread().setContextClassLoader( cl );
		}
	}

	@Override
	public ClassLoader getCL(ScriptletAsset s) {
		
		final BundleService.Bundle b = _sbundle.getBundle(s.getGroupId());
		
		return ( b != null ) ? b.getClassLoader() : Thread.currentThread().getContextClassLoader();
	}

	
}
